/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.sarco.bwi.lib;

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author iktsup
 */
public class Blender {

    private static String PARAM_BG = "-b";
    private static String PARAM_START_FRAME = "-s";
    private static String PARAM_END_FRAME = "-e";
    private static String PARAM_PATH = "-o";
    private static String PARAM_FORMAT = "-F";
    private static String PARAM_ADD_FILE_EXT = "-x";
    private static String PARAM_ANIMATION = "-a";
    private static String PARAM_DEBUG = "-d";
    private static String MSG_APPEND_FRAME = "Append frame ";
    private static String MSG_TIME = "Time: ";
    private static String SPACE = " ";
    /**
     * The default path to render the animation too.
     */
    public static String DEFAULT_PATH = "//render_#";
    private String blenderPath;
    private String file;
    private int startFrame;
    private int endFrame;
    private String path;
    private String format;
    private int addFileExt;
    private Mysql mysql;
    private Job job;
    private Config config;
    /**
     * @deprecated v0.0.5a
     */
    private Socket client;
    /**
     * @deprecated v0.0.5a
     */
    private Node node;
    /**
     * @deprecated v0.0.5a
     */
    private boolean hasSocket = false;
    private String command;

    /**
     * Creates a new Blender instance
     * @param blenderPath The path to the blender executeable
     * @param file Which .blend file to render
     * @param startFrame Starting frame to render
     * @param endFrame Last frame to render
     * @param path Path to the rendered file(s),
     * it follows the blender arguments syntax, see blender's manual for details.
     * @param format Which file format to render to. It follows the blender
     * arguments syntax, see blender's manual for details.
     * @param addFileExt 0 or 1, if to automatically add the file extension to
     * the rendered file(s).
     * @param mysql The <code>Mysql</code> class file to update job status.
     * @param config the <code>Config</code> class file which is presumed to
     * contain information about mysql username, mysql password, mysql  
     * ip/hostname and mysql the database to use.
     * @param job The <code>Job</code> class file which is presumed to contain 
     * information about start frame and end frame. This class file is also used
     *  to update the mysql table entry ('job').
     */
    public Blender(String blenderPath, String file, String path, String format,
            int addFileExt, Mysql mysql, Config config, Job job) {

        System.out.println("Starting blender init!");

        this.blenderPath = blenderPath;
        this.file = file;
        this.startFrame = job.getStartframe();
        this.endFrame = job.getEndframe();
        this.path = path;
        this.format = format;
        this.addFileExt = addFileExt;
        this.mysql = mysql;
        this.job = job;
        this.config = config;

        this.command = this.blenderPath
                + Blender.SPACE
                + Blender.PARAM_BG + Blender.SPACE + this.file
                + Blender.SPACE
                + Blender.PARAM_PATH + Blender.SPACE + this.path
                + Blender.SPACE
                + Blender.PARAM_FORMAT + Blender.SPACE + this.format
                + Blender.SPACE
                + Blender.PARAM_ADD_FILE_EXT + Blender.SPACE + this.addFileExt
                + Blender.SPACE
                + Blender.PARAM_START_FRAME + Blender.SPACE + this.startFrame
                + Blender.SPACE
                + Blender.PARAM_END_FRAME + Blender.SPACE + this.endFrame
                + Blender.SPACE
                + Blender.PARAM_ANIMATION;

        System.out.println("Ending blender init!");
    }

    /**
     * Sets the path to where the Blender executeable
     * is located.
     * @param blenderPath Path the the Blender executeable
     */
    public void setBlenderPath(String blenderPath) {
        this.blenderPath = blenderPath;
    }

    /**
     *
     * @return Gets the path to where the Blender executeable is located.
     */
    public String getBlenderPath() {
        return this.blenderPath;
    }

    /**
     * Starts the render job
     */
    public void render() throws SQLException, ParseException {
        boolean validCommand = true;
        System.out.println("Starting render!");

        if (this.getBlenderPath() == null) {
            validCommand = false;
        }
        if (this.getEndFrame() < this.getStartFrame()) {
            this.setEndFrame(getStartFrame());
            System.out.println(
                    "WARNING! EndFrame is less than StartFrame!");
            System.out.println(
                    "WARNING! Setting EndFrame to equal of StartFrame");
        }
        if (this.getFile() == null) {
            validCommand = false;
        }
        if (this.getFormat() == null) {
            validCommand = false;
        }
        if (this.getPath() == null) {
            this.setPath(Blender.DEFAULT_PATH);
            System.out.println("WARNING! No output set!");
            System.out.println("WARNING! Setting output to \""
                    + Blender.DEFAULT_PATH + "\"");
        }

        if (validCommand) {
            try {
                String bl = config.getBlenderPath();
                String bla = command.substring(bl.length(),
                        command.length());
                String blc = (job.getClientos().contains("Windows") ? 
                        ("start \"Blender\" " + "'" + bl + "'" + bla) : command);
                ProcessBuilder prb = new ProcessBuilder(command);
                Runtime run = Runtime.getRuntime();
                Process pr = run.exec(command);
                BufferedReader buf = new BufferedReader(new InputStreamReader(pr.getInputStream()));
                String line = null;
                while ((line = buf.readLine()) != null) {
                    if (line.startsWith(Blender.MSG_APPEND_FRAME)) {
                        System.out.println(line);

                        int frame = 0;
                        {
                            String s = line.substring(Blender.MSG_APPEND_FRAME.length());
                            s = s.substring(0, s.indexOf(Blender.MSG_TIME));
                            s = s.replaceAll(" ", "");
                            frame = Integer.parseInt(s);
                        }

                        String sTime = line.substring(
                                Blender.MSG_APPEND_FRAME.length() + String.valueOf(frame).length());
                        sTime = sTime.substring(Blender.MSG_TIME.length());

                        getTimeLeft(frame, sTime);
                    }
                    
                    {
                        mysql.connect(config.getMysql_host(), 
                                config.getMysql_user(), 
                                config.getMysql_pass(), 
                                config.getMysql_db());
                        Job currentJob = mysql.getJobEntry(job.getIdjob());
                        if (currentJob != null) {
                            if (currentJob.isAbortrequested()) {
                                currentJob.setTimefinished(FileManagement.getCurrentDate());
                                mysql.updateJobEntry(currentJob);
                                System.out.println("Render is abort requsted, aborting..");
                                BufferedOutputStream out = 
                                        new BufferedOutputStream(pr.getOutputStream());
                                out.write("^c".getBytes());
                                out.close();
                                pr.destroy();
                                return;
                            }
                        }
                        mysql.closeConnection();
                    }
                }

                pr.destroy();
            } catch (IOException ex) {
                Logger.getLogger(Blender.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }

    /**
     *
     * @param currFrame
     * @param sTime
     */
    public void getTimeLeft(int currFrame, String sTime) throws SQLException, ParseException {
        double hour = 0;
        double min = 0;
        double sec = 0;
        String[] times = sTime.split(":");
        if (!times[0].isEmpty()) {
            min = Double.parseDouble(times[0]);
        }
        if (!times[1].isEmpty()) {
            String ss = times[1];
            sec = Double.parseDouble(times[1]);
        }
        int lMin = 0;
        int lSec = 0;
        int lHou = 0;
        int lDay = 0;
        double s = (sec * (this.getEndFrame() - currFrame));
        double m = s / 60;
        double h = m / 60;
        double d = h / 24;
        if (m < 0.) {
            lMin = 0;
        } else {
            lMin = (int) m;
        }
        if (h < 0.) {
            lHou = 0;
        } else {
            lHou = (int) h;
        }
        if (d < 0.) {
            lDay = 0;
        } else {
            lDay = (int) d;
        }
        if (s < 60) {
            lSec = (int) s;
        } else {
            lSec = (int) (s % 60);
        }
        if (m > 59) {
            lMin = (int) (m % 60);
        }
        if (h > 23) {
            lHou = (int) (h % 24);
        }

        String sTimeLeft = "Time left: " + 
                lDay + "d:" + 
                lHou + "h:" + 
                lMin + "m:" + 
                lSec + "s";
        System.out.println(sTimeLeft);
        double dPercentDone = 0.0;
        // Percent calculation:
        {
            int S = this.getStartFrame();
            int E = this.getEndFrame();
            int D = E - S;
            int C = currFrame;
            double P = (double) E - (double) C;
            P /= (double) D;
            P *= 100;
            dPercentDone = 100 - P;
        }
        String sPercentDone = String.valueOf(dPercentDone);
        sPercentDone += "00";
        sPercentDone = sPercentDone.substring(0, sPercentDone.indexOf(".") + 3);

        sPercentDone += "% Done!";
        System.out.println(sPercentDone);

        // Update mysql database entry:
        if (mysql != null && job != null) {
            /**
             * Open an connection if the connection is closed.<br>
             * The <code>Statement</code> is set to <code>null</code> in
             * <code>mysql.closeConnection</code>
             */
            if (mysql.getStatement() == null) {
                mysql.connect(config.getMysql_host(), config.getMysql_user(),
                        config.getMysql_pass(), config.getMysql_db());
            }

            job.setPercentdone((int) dPercentDone);
            job.setTimeleft(sTimeLeft);

            mysql.updateJobEntry(job);

            // Close connection:
            mysql.closeConnection();
        }
    }

    /**
     * @return the file
     */
    public String getFile() {
        return file;
    }

    /**
     * @param file the file to set
     */
    public void setFile(String file) {
        this.file = file;
    }

    /**
     * @return the startFrame
     */
    public int getStartFrame() {
        return startFrame;
    }

    /**
     * @param startFrame the startFrame to set
     */
    public void setStartFrame(int startFrame) {
        this.startFrame = startFrame;
    }

    /**
     * @return the endFrame
     */
    public int getEndFrame() {
        return endFrame;
    }

    /**
     * @param endFrame the endFrame to set
     */
    public void setEndFrame(int endFrame) {
        this.endFrame = endFrame;
    }

    /**
     * @return the path
     */
    public String getPath() {
        return path;
    }

    /**
     * @param path the path to set
     */
    public void setPath(String path) {
        this.path = path;
    }

    /**
     * @return the format
     */
    public String getFormat() {
        return format;
    }

    /**
     * @param format the format to set
     */
    public void setFormat(String format) {
        this.format = format;
    }

    /**
     * @return the addFileExt
     */
    public int getAddFileExt() {
        return addFileExt;
    }

    /**
     * @param addFileExt the addFileExt to set
     */
    public void setAddFileExt(int addFileExt) {
        this.addFileExt = addFileExt;
    }

    /**
     * @return the node
     */
    public Node getNode() {
        return node;
    }
}
